/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* eggtrayicon.c
 * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include <config.h>
#include <string.h>
#include <libintl.h>

#include <gtk/gtkversion.h>

#if ! GTK_CHECK_VERSION(2,10,0)
/* for Gtk+ version < 2.10, we need EggTrayIcon */

#include "eggtrayicon.h"

#include <gdkconfig.h>
#if defined (GDK_WINDOWING_X11)
#include <gdk/gdkx.h>
#include <X11/Xatom.h>
#elif defined (GDK_WINDOWING_WIN32)
#include <gdk/gdkwin32.h>
#endif

#ifndef EGG_COMPILATION
#ifndef _
#define _(x) dgettext (GETTEXT_PACKAGE, x)
#define N_(x) x
#endif
#else
#define _(x) x
#define N_(x) x
#endif

#define SYSTEM_TRAY_REQUEST_DOCK    0
#define SYSTEM_TRAY_BEGIN_MESSAGE   1
#define SYSTEM_TRAY_CANCEL_MESSAGE  2

#define SYSTEM_TRAY_ORIENTATION_HORZ 0
#define SYSTEM_TRAY_ORIENTATION_VERT 1

enum {
  PROP_0,
  PROP_ORIENTATION
};

static GtkPlugClass *parent_class = NULL;

static void egg_tray_icon_init(EggTrayIcon *icon);
static void egg_tray_icon_class_init(EggTrayIconClass *klass);

static void egg_tray_icon_get_property(GObject    *object,
                                       guint       prop_id,
                                       GValue     *value,
                                       GParamSpec *pspec);

static void egg_tray_icon_realize(GtkWidget *widget);
static void egg_tray_icon_unrealize(GtkWidget *widget);

static void egg_tray_icon_add(GtkContainer *container,
                              GtkWidget    *widget);

#ifdef GDK_WINDOWING_X11
static void egg_tray_icon_update_manager_window(EggTrayIcon *icon,
    gboolean     dock_if_realized);
static void egg_tray_icon_manager_window_destroyed(EggTrayIcon *icon);
#endif

GType
egg_tray_icon_get_type(void)
{
  static GType our_type = 0;

  if (our_type == 0) {
    static const GTypeInfo our_info = {
      sizeof(EggTrayIconClass),
      (GBaseInitFunc) NULL,
      (GBaseFinalizeFunc) NULL,
      (GClassInitFunc) egg_tray_icon_class_init,
      NULL, /* class_finalize */
      NULL, /* class_data */
      sizeof(EggTrayIcon),
      0,    /* n_preallocs */
      (GInstanceInitFunc) egg_tray_icon_init
    };

    our_type = g_type_register_static(GTK_TYPE_PLUG, "EggTrayIcon", &our_info, 0);
  }

  return our_type;
}

static void
egg_tray_icon_init(EggTrayIcon *icon)
{
  icon->stamp = 1;
  icon->orientation = GTK_ORIENTATION_HORIZONTAL;

  gtk_widget_add_events(GTK_WIDGET(icon), GDK_PROPERTY_CHANGE_MASK);
}

static void
egg_tray_icon_class_init(EggTrayIconClass *klass)
{
  GObjectClass *gobject_class = (GObjectClass *)klass;
  GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
  GtkContainerClass *container_class = (GtkContainerClass *)klass;

  parent_class = g_type_class_peek_parent(klass);

  gobject_class->get_property = egg_tray_icon_get_property;

  widget_class->realize   = egg_tray_icon_realize;
  widget_class->unrealize = egg_tray_icon_unrealize;

  container_class->add = egg_tray_icon_add;

  g_object_class_install_property(gobject_class,
                                  PROP_ORIENTATION,
                                  g_param_spec_enum("orientation",
                                                    _("Orientation"),
                                                    _("The orientation of the tray."),
                                                    GTK_TYPE_ORIENTATION,
                                                    GTK_ORIENTATION_HORIZONTAL,
                                                    G_PARAM_READABLE));

#if defined (GDK_WINDOWING_X11)
  /* Nothing */
#elif defined (GDK_WINDOWING_WIN32)
  g_warning("Port eggtrayicon to Win32");
#else
  g_warning("Port eggtrayicon to this GTK+ backend");
#endif
}

static void
egg_tray_icon_get_property(GObject    *object,
                           guint       prop_id,
                           GValue     *value,
                           GParamSpec *pspec)
{
  EggTrayIcon *icon = EGG_TRAY_ICON(object);

  switch (prop_id) {
    case PROP_ORIENTATION:
      g_value_set_enum(value, icon->orientation);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
      break;
  }
}

#ifdef GDK_WINDOWING_X11

static void
egg_tray_icon_get_orientation_property(EggTrayIcon *icon)
{
  Display *xdisplay;
  Atom type;
  int format;
  union {
    gulong *prop;
    guchar *prop_ch;
  } prop = { NULL };
  gulong nitems;
  gulong bytes_after;
  int error, result;

  g_assert(icon->manager_window != None);

  xdisplay = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(GTK_WIDGET(icon)));

  gdk_error_trap_push();
  type = None;
  result = XGetWindowProperty(xdisplay,
                              icon->manager_window,
                              icon->orientation_atom,
                              0, G_MAXLONG, FALSE,
                              XA_CARDINAL,
                              &type, &format, &nitems,
                              &bytes_after, &(prop.prop_ch));
  error = gdk_error_trap_pop();

  if (error || result != Success)
    return;

  if (type == XA_CARDINAL) {
    GtkOrientation orientation;

    orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ?
                  GTK_ORIENTATION_HORIZONTAL :
                  GTK_ORIENTATION_VERTICAL;

    if (icon->orientation != orientation) {
      icon->orientation = orientation;

      g_object_notify(G_OBJECT(icon), "orientation");
    }
  }

  if (prop.prop)
    XFree(prop.prop);
}

static GdkFilterReturn
egg_tray_icon_manager_filter(GdkXEvent *xevent, GdkEvent *event, gpointer user_data)
{
  EggTrayIcon *icon = user_data;
  XEvent *xev = (XEvent *)xevent;

  if (xev->xany.type == ClientMessage &&
      xev->xclient.message_type == icon->manager_atom &&
      xev->xclient.data.l[1] == icon->selection_atom) {
    egg_tray_icon_update_manager_window(icon, TRUE);
  } else if (xev->xany.window == icon->manager_window) {
    if (xev->xany.type == PropertyNotify &&
        xev->xproperty.atom == icon->orientation_atom) {
      egg_tray_icon_get_orientation_property(icon);
    }
    if (xev->xany.type == DestroyNotify) {
      egg_tray_icon_manager_window_destroyed(icon);
    }
  }
  return GDK_FILTER_CONTINUE;
}

#endif

static void
egg_tray_icon_unrealize(GtkWidget *widget)
{
#ifdef GDK_WINDOWING_X11
  EggTrayIcon *icon = EGG_TRAY_ICON(widget);
  GdkWindow *root_window;

  if (icon->manager_window != None) {
    GdkWindow *gdkwin;

    gdkwin = gdk_window_lookup_for_display(gtk_widget_get_display(widget),
                                           icon->manager_window);

    gdk_window_remove_filter(gdkwin, egg_tray_icon_manager_filter, icon);
  }

  root_window = gdk_screen_get_root_window(gtk_widget_get_screen(widget));

  gdk_window_remove_filter(root_window, egg_tray_icon_manager_filter, icon);

  if (GTK_WIDGET_CLASS(parent_class)->unrealize)
    (* GTK_WIDGET_CLASS(parent_class)->unrealize)(widget);
#endif
}

#ifdef GDK_WINDOWING_X11

static void
egg_tray_icon_send_manager_message(EggTrayIcon *icon,
                                   long         message,
                                   Window       window,
                                   long         data1,
                                   long         data2,
                                   long         data3)
{
  XClientMessageEvent ev;
  Display *display;

  ev.type = ClientMessage;
  ev.window = window;
  ev.message_type = icon->system_tray_opcode_atom;
  ev.format = 32;
  ev.data.l[0] = gdk_x11_get_server_time(GTK_WIDGET(icon)->window);
  ev.data.l[1] = message;
  ev.data.l[2] = data1;
  ev.data.l[3] = data2;
  ev.data.l[4] = data3;

  display = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(GTK_WIDGET(icon)));

  gdk_error_trap_push();
  XSendEvent(display,
             icon->manager_window, False, NoEventMask, (XEvent *)&ev);
  XSync(display, False);
  gdk_error_trap_pop();
}

static void
egg_tray_icon_send_dock_request(EggTrayIcon *icon)
{
  egg_tray_icon_send_manager_message(icon,
                                     SYSTEM_TRAY_REQUEST_DOCK,
                                     icon->manager_window,
                                     gtk_plug_get_id(GTK_PLUG(icon)),
                                     0, 0);
}

static void
egg_tray_icon_update_manager_window(EggTrayIcon *icon,
                                    gboolean     dock_if_realized)
{
  Display *xdisplay;

  if (icon->manager_window != None)
    return;

  xdisplay = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(GTK_WIDGET(icon)));

  XGrabServer(xdisplay);

  icon->manager_window = XGetSelectionOwner(xdisplay,
                         icon->selection_atom);

  if (icon->manager_window != None)
    XSelectInput(xdisplay,
                 icon->manager_window, StructureNotifyMask|PropertyChangeMask);

  XUngrabServer(xdisplay);
  XFlush(xdisplay);

  if (icon->manager_window != None) {
    GdkWindow *gdkwin;

    gdkwin = gdk_window_lookup_for_display(gtk_widget_get_display(GTK_WIDGET(icon)),
                                           icon->manager_window);

    gdk_window_add_filter(gdkwin, egg_tray_icon_manager_filter, icon);

    if (dock_if_realized && GTK_WIDGET_REALIZED(icon))
      egg_tray_icon_send_dock_request(icon);

    egg_tray_icon_get_orientation_property(icon);
  }
}

static void
egg_tray_icon_manager_window_destroyed(EggTrayIcon *icon)
{
  GdkWindow *gdkwin;

  g_return_if_fail(icon->manager_window != None);

  gdkwin = gdk_window_lookup_for_display(gtk_widget_get_display(GTK_WIDGET(icon)),
                                         icon->manager_window);

  gdk_window_remove_filter(gdkwin, egg_tray_icon_manager_filter, icon);

  icon->manager_window = None;

  egg_tray_icon_update_manager_window(icon, TRUE);
}

#endif

static gboolean
transparent_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
{
  gdk_window_clear_area(widget->window, event->area.x, event->area.y,
                        event->area.width, event->area.height);
  return FALSE;
}

static void
make_transparent_again(GtkWidget *widget, GtkStyle *previous_style,
                       gpointer user_data)
{
  gdk_window_set_back_pixmap(widget->window, NULL, TRUE);
}

static void
make_transparent(GtkWidget *widget, gpointer user_data)
{
  if (GTK_WIDGET_NO_WINDOW(widget) || GTK_WIDGET_APP_PAINTABLE(widget))
    return;

  gtk_widget_set_app_paintable(widget, TRUE);
  gtk_widget_set_double_buffered(widget, FALSE);
  gdk_window_set_back_pixmap(widget->window, NULL, TRUE);
  g_signal_connect(widget, "expose_event",
                   G_CALLBACK(transparent_expose_event), NULL);
  g_signal_connect_after(widget, "style_set",
                         G_CALLBACK(make_transparent_again), NULL);
}

static void
egg_tray_icon_realize(GtkWidget *widget)
{
#ifdef GDK_WINDOWING_X11
  EggTrayIcon *icon = EGG_TRAY_ICON(widget);
  GdkScreen *screen;
  GdkDisplay *display;
  Display *xdisplay;
  char buffer[256];
  GdkWindow *root_window;

  if (GTK_WIDGET_CLASS(parent_class)->realize)
    GTK_WIDGET_CLASS(parent_class)->realize(widget);

  make_transparent(widget, NULL);

  screen = gtk_widget_get_screen(widget);
  display = gdk_screen_get_display(screen);
  xdisplay = gdk_x11_display_get_xdisplay(display);

  /* Now see if there's a manager window around */
  g_snprintf(buffer, sizeof(buffer),
             "_NET_SYSTEM_TRAY_S%d",
             gdk_screen_get_number(screen));

  icon->selection_atom = XInternAtom(xdisplay, buffer, False);

  icon->manager_atom = XInternAtom(xdisplay, "MANAGER", False);

  icon->system_tray_opcode_atom = XInternAtom(xdisplay,
                                  "_NET_SYSTEM_TRAY_OPCODE",
                                  False);

  icon->orientation_atom = XInternAtom(xdisplay,
                                       "_NET_SYSTEM_TRAY_ORIENTATION",
                                       False);

  egg_tray_icon_update_manager_window(icon, FALSE);
  egg_tray_icon_send_dock_request(icon);

  root_window = gdk_screen_get_root_window(screen);

  /* Add a root window filter so that we get changes on MANAGER */
  gdk_window_add_filter(root_window,
                        egg_tray_icon_manager_filter, icon);
#endif
}

static void
egg_tray_icon_add(GtkContainer *container, GtkWidget *widget)
{
  g_signal_connect(widget, "realize",
                   G_CALLBACK(make_transparent), NULL);
  GTK_CONTAINER_CLASS(parent_class)->add(container, widget);
}

EggTrayIcon *
egg_tray_icon_new_for_screen(GdkScreen *screen, const char *name)
{
  g_return_val_if_fail(GDK_IS_SCREEN(screen), NULL);

  return g_object_new(EGG_TYPE_TRAY_ICON, "screen", screen, "title", name, NULL);
}

EggTrayIcon*
egg_tray_icon_new(const gchar *name)
{
  return g_object_new(EGG_TYPE_TRAY_ICON, "title", name, NULL);
}

guint
egg_tray_icon_send_message(EggTrayIcon *icon,
                           gint         timeout,
                           const gchar *message,
                           gint         len)
{
  guint stamp;

  g_return_val_if_fail(EGG_IS_TRAY_ICON(icon), 0);
  g_return_val_if_fail(timeout >= 0, 0);
  g_return_val_if_fail(message != NULL, 0);

#ifdef GDK_WINDOWING_X11
  if (icon->manager_window == None)
    return 0;
#endif

  if (len < 0)
    len = strlen(message);

  stamp = icon->stamp++;

#ifdef GDK_WINDOWING_X11
  /* Get ready to send the message */
  egg_tray_icon_send_manager_message(icon, SYSTEM_TRAY_BEGIN_MESSAGE,
                                     icon->manager_window,
                                     timeout, len, stamp);

  /* Now to send the actual message */
  gdk_error_trap_push();
  while (len > 0) {
    XClientMessageEvent ev;
    Display *xdisplay;

    xdisplay = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(GTK_WIDGET(icon)));

    ev.type = ClientMessage;
    ev.window = icon->manager_window;
    ev.format = 8;
    ev.message_type = XInternAtom(xdisplay,
                                  "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
    if (len > 20) {
      memcpy(&ev.data, message, 20);
      len -= 20;
      message += 20;
    } else {
      memcpy(&ev.data, message, len);
      len = 0;
    }

    XSendEvent(xdisplay,
               icon->manager_window, False, StructureNotifyMask, (XEvent *)&ev);
    XSync(xdisplay, False);
  }
  gdk_error_trap_pop();
#endif

  return stamp;
}

void
egg_tray_icon_cancel_message(EggTrayIcon *icon,
                             guint        id)
{
  g_return_if_fail(EGG_IS_TRAY_ICON(icon));
  g_return_if_fail(id > 0);
#ifdef GDK_WINDOWING_X11
  egg_tray_icon_send_manager_message(icon, SYSTEM_TRAY_CANCEL_MESSAGE,
                                     (Window)gtk_plug_get_id(GTK_PLUG(icon)),
                                     id, 0, 0);
#endif
}

GtkOrientation
egg_tray_icon_get_orientation(EggTrayIcon *icon)
{
  g_return_val_if_fail(EGG_IS_TRAY_ICON(icon), GTK_ORIENTATION_HORIZONTAL);

  return icon->orientation;
}

#endif /* ! GTK_CHECK_VERSION(2,10,0) */

